/**
* Copyright (c) Codice Foundation
* <p/>
* This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser
* General Public License as published by the Free Software Foundation, either version 3 of the
* License, or any later version.
* <p/>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. A copy of the GNU Lesser General Public License
* is distributed along with this program and can be found at
* <http://www.gnu.org/licenses/lgpl.html>.
*/
package org.codice.ddf.commands.catalog;
import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.apache.felix.gogo.commands.Option;
import org.codice.ddf.commands.catalog.facade.CatalogFacade;
import org.fusesource.jansi.Ansi;
import org.geotools.filter.text.cql2.CQL;
import org.joda.time.DateTime;
import org.opengis.filter.Filter;
import ddf.catalog.data.Metacard;
import ddf.catalog.data.Result;
import ddf.catalog.operation.SourceResponse;
import ddf.catalog.operation.impl.QueryImpl;
import ddf.catalog.operation.impl.QueryRequestImpl;
import ddf.util.XPathHelper;
@Command(scope = CatalogCommands.NAMESPACE, name = "search", description = "Searches records in the catalog provider.")
public class SearchCommand extends CatalogCommands {
private static final String ID = "ID ";
private static final String TITLE = "Title ";
private static final String DATE = "Modified ";
private static final String EXCERPT = "Excerpt ";
private static final int TITLE_MAX_LENGTH = 30;
private static final int EXCERPT_MAX_LENGTH = 50;
@Argument(name = "NUMBER_OF_ITEMS", description = "Number of maximum records to display.", index = 1, multiValued = false, required = false)
int numberOfItems = -1;
@Argument(name = "SEARCH_PHRASE", index = 0, multiValued = false, required = false,
description = "Phrase to query the catalog provider.")
String searchPhrase = null;
@Option(name = "case-sensitive", required = false, aliases = {
"-c"}, multiValued = false, description = "Makes the search case sensitive. NOTE: Does not apply to CQL filters")
boolean caseSensitive = false;
@Option(name = "--cql", required = false, aliases = {}, multiValued = false, description =
"Search using CQL Filter expressions.\n" + "CQL Examples:\n"
+ "\tTextual: search --cql \"title like 'some text'\"\n"
+ "\tTemporal: search --cql \"modified before 2012-09-01T12:30:00Z\"\n"
+ "\tSpatial: search --cql \"DWITHIN(location, POINT (1 2) , 10, kilometers)\"\n"
+ "\tComplex: search --cql \"title like 'some text' AND modified before 2012-09-01T12:30:00Z\"")
String cqlFilter = null;
@Override
protected Object doExecute() throws Exception {
String formatString =
"%1$-33s %2$-26s %3$-" + TITLE_MAX_LENGTH + "s %4$-" + EXCERPT_MAX_LENGTH + "s%n";
CatalogFacade catalogProvider = getCatalog();
Filter filter = null;
if (cqlFilter != null) {
filter = CQL.toFilter(cqlFilter);
} else {
if (searchPhrase == null) {
searchPhrase = "*";
}
if (caseSensitive) {
filter = getFilterBuilder().attribute(Metacard.ANY_TEXT).is().like()
.caseSensitiveText(searchPhrase);
} else {
filter = getFilterBuilder().attribute(Metacard.ANY_TEXT).is().like()
.text(searchPhrase);
}
}
QueryImpl query = new QueryImpl(filter);
query.setRequestsTotalResultsCount(true);
if (numberOfItems > -1) {
query.setPageSize(numberOfItems);
}
long start = System.currentTimeMillis();
SourceResponse response = catalogProvider.query(new QueryRequestImpl(query));
long end = System.currentTimeMillis();
int size = 0;
if (response.getResults() != null) {
size = response.getResults().size();
}
console.println();
console.printf(" %d result(s) out of %s%d%s in %3.3f seconds", (size),
Ansi.ansi().fg(Ansi.Color.CYAN).toString(), response.getHits(),
Ansi.ansi().reset().toString(), (end - start) / MILLISECONDS_PER_SECOND);
console.printf(formatString, "", "", "", "");
printHeaderMessage(String.format(formatString, ID, DATE, TITLE, EXCERPT));
for (Result result : response.getResults()) {
Metacard metacard = result.getMetacard();
String title = (metacard.getTitle() != null ? metacard.getTitle() : "N/A");
String excerpt = "N/A";
String modifiedDate = "";
if (searchPhrase != null) {
if (metacard.getMetadata() != null) {
XPathHelper helper = new XPathHelper(metacard.getMetadata());
String indexedText = helper.getDocument().getDocumentElement().getTextContent();
indexedText = indexedText.replaceAll("\\r\\n|\\r|\\n", " ");
String normalizedSearchPhrase = searchPhrase.replaceAll("\\*", "");
int index = -1;
if (caseSensitive) {
index = indexedText.indexOf(normalizedSearchPhrase);
} else {
index = indexedText.toLowerCase()
.indexOf(normalizedSearchPhrase.toLowerCase());
}
if (index != -1) {
int contextLength =
(EXCERPT_MAX_LENGTH - normalizedSearchPhrase.length() - 8) / 2;
excerpt = "..." + indexedText
.substring(Math.max(index - contextLength, 0), index);
excerpt = excerpt + Ansi.ansi().fg(Ansi.Color.GREEN).toString();
excerpt = excerpt + indexedText
.substring(index, index + normalizedSearchPhrase.length());
excerpt = excerpt + Ansi.ansi().reset().toString();
excerpt = excerpt + indexedText
.substring(index + normalizedSearchPhrase.length(),
Math.min(indexedText.length(),
index + normalizedSearchPhrase.length()
+ contextLength)) + "...";
}
}
}
if (metacard.getModifiedDate() != null) {
modifiedDate = new DateTime(metacard.getModifiedDate().getTime())
.toString(DATETIME_FORMATTER);
}
console.printf(formatString, metacard.getId(), modifiedDate,
title.substring(0, Math.min(title.length(), TITLE_MAX_LENGTH)), excerpt);
}
return null;
}
}